/*
 * Controls input and output 
 */
package config;

import Protocol.*;
import java.io.*;
import java.net.Socket;

/**
 * Connects a Client to the server. 
 * Controls all input and output.
 * @author Joep Kemperman, Kay van Bree
 * @version 0.3
 */
public class Connection {
	private static Connection connection;
	private String url;
	private int port;
	private boolean isConnected;
	
	private Socket socket;
	private InputStream input;
	private OutputStream output;
	private BufferedReader reader;
	private PrintWriter writer;
	private ObjectInputStream objectInput;
	private ObjectOutputStream objectOutput;
	
	/**
	 * The connection
	 * 
	 * @author Kay van Bree
	 * @param url The server url
	 * @param port The server port
	 * @param log The log
	 * @since 0.1
	 */
	private Connection(){}
	
	/**
	 * Return the connection
	 * This makes it Singeton!
	 * @author Kay van Bree
	 * @since 0.2
	 * @return Connection connection
	 */
	public static synchronized Connection getConnection(){
		if (connection == null){
			connection = new Connection();
		}
		return connection;	
	}
	
	/**
	 * Set stuff for connection
	 * @author Kay van Bree
	 * @param url
	 * @param port 
	 */
	public void setConnection(String url, int port){
		this.url = url;
		this.port = port;
		
		this.isConnected = false;
		
		initialize();
		connect();
	}
	
	/**
	 * No, you can't clone
	 * @author Kay van Bree
	 * @since 0.2
	 * @return Nothing hahaha!
	 * @throws CloneNotSupportedException 
	 */
	@Override
	public Object clone() throws CloneNotSupportedException {
		throw new CloneNotSupportedException(); 
	}
	
	public boolean isConnected(){
		return isConnected;
	}
	
	/**
	 * Restart the connection
	 * 
	 * @author Kay van Bree
	 * @since 0.2
	 */
	public void restart(){
		close(true);
		initialize();
		connect();
	}
	
	/**
	 * Closes connection
	 * 
	 * @author Kay van Bree
	 * @since 0.2
	 */
	public void close(){
		close(false);
	}
	
	/**
	 * Closes communication
	 * @author Joep Kemperman, Kay van Bree
	 * @since 0.2
	 * @param restart Reconnects after closing communication if true.
	 */
	public void close(boolean restart){
		if (isConnected == true) {
			Content.log.println("Closing...");
			try {
				input.close();
				output.close();
				isConnected = false;
				
				if (restart == true)
					connect();
			} catch (IOException ioe) {
				Content.log.println("Failed to close connection: " + ioe.getMessage());
			} catch (NullPointerException npe) {
				Content.log.println("Failed to close communication: There is no connection.");
			}
		} else
			Content.log.println("Failed to close communication: There is no connection.");
	}
	
	/**
	 * Creates a new Socket and prints exception messages.
	 * 
	 * @author Kay van Bree
	 * @since 0.1
	 */
	private void initialize(){
		Content.log.println("Initializing...");
		try {
			this.socket = new Socket(url, port);
			Content.log.println("Connected to socket: " + url + ":" + port);
		} catch(IOException ioe){
			Content.log.println("Failed to connect to socket.");
		} catch(NullPointerException npe){
			Content.log.println("Failed to connect to socket.");
		}			
	}
	
	/**
	 * Connects to the server
	 * Creates input and output streams
	 * 
	 * @author Joep Kemperman, Kay van Bree
	 * @since 0.1
	 */
	private void connect() {
		if (socket != null) {
			try {
				// Get inputs
				input = socket.getInputStream();
				output = socket.getOutputStream();
				
				// Get reader & writer
				reader = new BufferedReader(new InputStreamReader(input));
				writer = new PrintWriter(output, true);
				
				// Create Object I/O
				objectOutput = new ObjectOutputStream(output);
				output.flush(); // Sends any buffered data
				objectInput = new ObjectInputStream(input);
				
				System.out.println("Objectinput and output set.");
				// Connection has been set up
				isConnected = true;
				Content.log.print("Communication has been set up.\n");
			} catch (IOException ioe) {
				Content.log.print("Could not connect.\n");
			}
		}
	}
	
	/**
	 * Sends and receives a packet
	 * 
	 * @author Kay van Bree
	 * @since 0.2
	 * @param packet Packet to be send
	 */
	public Object communicate(BasicPacket packet) throws Exception {
		Object result = null;
		try {
			Content.log.println("Sending a packet. " + packet.toString());
			objectOutput.writeObject(packet);
			result = receive();
		} catch (IOException ex) {
			throw new Exception("Sending failed: " + ex.getMessage());
		} catch (NullPointerException npe){
			throw new Exception("Not connected");
		}
		
		return result;
	}
	
	/**
	 * Sends a packet
	 * @author Joep Kemperman
	 * @param packet Packet to be send
	 */
	public void send(BasicPacket packet) throws Exception {
		try {
			Content.log.println("Sending a packet. " + packet.toString());
			objectOutput.writeObject(packet);
		} catch (IOException ex) {
			throw new Exception("Sending failed: " + ex.getMessage());
		} catch (NullPointerException npe){
			throw new Exception("Not connected");
		}
	}
	
	/**
	 * Listens for an incoming packet and returns it.
	 * @author Joep Kemperman
	 * @return Received packet
	 */
	public Object receive() throws Exception {
		Object result = null;
		try {
			Content.log.println("Trying to receive...");
			result = objectInput.readObject();
			Content.log.println("Received packet. " + result.toString());
		} catch (ClassNotFoundException ex) {
			throw new Exception("Receiving failed...");
		} catch (NullPointerException npe){
			throw new Exception("Not connected");
		}
		
		return result;
	}

	/**
	 * To test sending our protocol objects
	 * @author Kay van Bree
	 * @param text
	 * @return Received string
	 */
	public String test(String text) {
		try {
			int type = PacketTypeKeeper.TESTPACKET;
			TestPacket packet = (TestPacket) PacketFactory.getPacket(type);
			packet.setMessage(text);
			Object result = communicate(packet);
			return result.toString();
		} catch (Exception ex) {
			Content.log.println(ex.getMessage());
			return ex.getMessage();
		}
	}

	/**
	 * Send the login packet
	 * @author Kay van Bree
	 * @param username
	 * @param password
	 * @return Did we login?
	 */
	public InlogResponsePacket login(String username, String password) {
		try {
			// Create and set the Packet
			InlogRequestPacket request = (InlogRequestPacket) PacketFactory.getPacket(PacketTypeKeeper.INLOGREQUESTPACKET);
			request.setUsername(username);
			request.setPassword(password);
			request.setTypeOfClient(0);
			
			// Send and receive the packet
			InlogResponsePacket response = (InlogResponsePacket) communicate(request);
			
			return response;
		} catch (Exception ex) {
			Content.log.println("Exception: "+ ex.getMessage());
			// Appearantly, something went wrong, we did not login
			return null;
		}
	}	
}
